LLVM-Clang 插件开发

iOS | LLVM | Objective-C

Clang 插件开发-准备

$ cd LLVM_ALL/llvm/tools/clang/tools
$ mkdir Your_Plugin_Dir_Name
新建文件夹(自己的插件,eg. 我的文件夹命名为 mz-plugin,后面以我自己的文件目录讲解,各位同学在实践的时候请注意替换为自己的文件夹目录)

clang/tools 目录下 CMakeLists.txt 文件最下方添加我们自己的插件目录: add_clang_subdirectory(mz-plugin),这样,clang 在编译时就会加载我们插件目录下的插件

$ cd mz-plugin
$ touch MZPlugin.cpp
$ touch CMakeLists.txt

编辑 CMakeLists.txt,添加下面这行:
add_llvm_loadable_module(MZPlugin MZPlugin.cpp)

1
2
3
4
5
6
// 如果有多个 cpp 文件,可以如下:
add_llvm_loadable_module(MZPlugin
MZPlugin1.cpp
MZPlugin2.cpp
MZPlugin3.cpp
)

编写插件

编写 C++ 文件,当然是用 IDE 方便了,在 llvm_xcode 目录下执行 cmake -G Xcode ../llvm 生成的模板工程,首次打开选择 Automatically Create Schemes 方式创建scheme
Automatically Create Schemes

找到我们自己的插件 cpp 文件 Loadable modules/MZPlugin/MZPlugin.cpp,编写插件:
插件cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"

using namespace clang;
using namespace std;
using namespace llvm;

namespace MZPlugin {

class MZConsumer : public ASTConsumer {
public:
// 重写 HandleTranslationUnit,在每编译完成一个语法树时都会调用此方法
void HandleTranslationUnit(ASTContext &Ctx) {
cout << "MZPlugin-HandlerTranslationUnit,编译完成一个语法树" << endl;
}
};

// 继承 PluginASTAction,重写两个方法
class MZAction: public PluginASTAction {
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef isFile) {
return unique_ptr<MZConsumer> (new MZConsumer);
}

bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
return true;
}
};
}

/**
注册插件

1.static clang::FrontendPluginRegistry::Add<MZPlugin::MZAction>
2.泛型 声明 Add 类型,指定 MZPlugin 的 MZAction
3.官方给的示例用的 X 就按官方的来吧

@param "MZPlugin" 插件名称
@param clang-plugin." 插件描述
*/
static FrontendPluginRegistry::Add<MZPlugin::MZAction>
X("MZPlugin", "The MZPlugin is my first clang-plugin.");

编译插件

编写完成之后,选择我们的插件 target,编译出 .dylib 插件文件:
编译插件

编译成功之后,会在 llvm_xcode/Debug/lib/ 目录下生成 MZPlugin.dylib文件

使用插件

在Xcode 项目中指定加载插件动态库:Build Settings -> Other C Flags:
-Xclang -load -Xclang 动态库全路径 -Xclang -add-plugin -Xclang 插件名称

eg.-Xclang -load -Xclang /Users/username/Desktop/项目/LLVM_ALL
/llvm_xcode/Debug/lib/MZPlugin.dylib -Xclang -add-plugin -Xclang MZPlugin

Other C Flags

已经安耐不住激动的心情了么,编译项目,结果会报错,因为 Xcode Build Settings Compiler for C/C++/Objective 默认编译器使用的是 Default compiler(Apple LLVM 9.0),不让我们选择我们自己的编译器,我们还需要最后一步,Hack Xcode,然后才能修改默认的编译器

Hack Xcode

下载 XcodeHacking.zip,解压,修改 HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec,将 ExecPath 位置 替换为我们刚才用 ninja 编译好的 clang 的全路径
eg. /Users/username/Desktop/项目/LLVM_ALL/llvm_release/bin/clang
HackedClang.xcspec

然后执行下面两行指令将这两个文件移至 Xcode 包中:
$ sudo mv HackedClang.xcplugin xcode-select -print-path/../PlugIns
/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins

$ sudo mv HackedBuildSystem.xcspec xcode-select -print-path/Platforms
/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications

完成之后,重启 Xcode,这时,我们就可以修改 Xcode 默认的编译器了
Compiler for C/C++/Objective

选择 Clang LLVM Trunk,然后进行编译,这时候我们观察下便以信息,会发现,刚才我们输出我们在 HandleTranslationUnit 方法中的信息已经成功输出
success

总结

至此我们已经完成了一个简单的插件开发,如果需要开发更牛逼的功能,我们还需要对编译原理、语义分析等进行更深入的了解,感兴趣的童鞋可以深入研究